﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Com.PhilChuang.Mobile.Apps.LocationSender.Model.Data;
using Com.PhilChuang.Mobile.Apps.LocationSender.Model.Services;
using Com.PhilChuang.Pmvp.Model;
using Com.PhilChuang.Pmvp.Model.Services;
using Com.PhilChuang.Pmvp.Utils;
using Com.PhilChuang.Utils;

namespace Com.PhilChuang.Mobile.Apps.LocationSender.Model
{
	public class LocationSenderApp : IApp, IDisposable
	{
		#region Bug Reporting / Feedback
		private static readonly IExceptionLogService s_ExceptionFileCheckService = AppServiceLocator.Instance.GetImpl<IExceptionLogService> ();

		public static bool HasExceptionLog
		{ get { return s_ExceptionFileCheckService.HasExceptionLog; } }

		public static String ExceptionLogText
		{ get { return s_ExceptionFileCheckService.GetExceptionLog (); } }
		#endregion

		#region Events
		// Main events
		public event Action<String> StatusChanged = delegate { }; 
		public event Action<GeoCoordinate> GeoCoordinateUpdated = delegate { };
		public event Action OnLocationServiceUnavailable = delegate { }; 
		// IApp events
		public event Action Launching = delegate { };
		public event Action Initialized = delegate { };
		public event Action Suspending = delegate { };
		public event Action Resuming = delegate { };
		public event Action ResumingFromTombstone = delegate { };
		public event Action Closing = delegate { };
		#endregion

		#region Properties
		public bool IsInitialized { get; private set; }
		public bool IsDisposed { get; private set; }

		public LocationSenderSettings Settings { get; private set; }
		public String Status { get; private set; }
		public GeoCoordinate CurrentPosition { get; private set; }
		public bool LocationServiceUnavailable { get; private set; }
		public bool HasSavedLastMessage
		{ get { return m_Dao != null && m_Dao.LoadGeoCoordinateFormat () != GeoCoordinateFormat.None; } } // && m_Dao.LoadRecipients ().IsNullOrEmpty (); } }
		#endregion

		#region Private members
		private readonly IList<IDisposable> m_Disposables = new List<IDisposable> ();
		private readonly Object lock_Init = new object ();
		private readonly Object lock_Dispose = new object ();

		private IAppHelper m_AppHelper;
		private ILocationSenderDao m_Dao;
		private IGeoCoordinateProvider m_GeoCoordinateProvider;
		private ISendMessageService m_SendMessageService;
		private ILaunchMarketplaceAppDetailsService myLaunchMarketplaceAppDetailsService;
		#endregion

		#region Constructors / Dispose
		public LocationSenderApp ()
		{
			IsInitialized = false;
			IsDisposed = false;

			m_AppHelper = AppServiceLocator.Instance.GetImpl<IAppHelper> ();
			m_AppHelper.UnhandledException += OnUnhandledException;
			m_AppHelper.Launching += OnLaunching;
			m_AppHelper.Suspending += OnSuspending;
			m_AppHelper.Resuming += OnResuming;
			m_AppHelper.ResumingFromTombstone += OnResumingFromTombstone;
			m_AppHelper.Closing += OnClosing;
		}

		public void Init (bool loadAsync)
		{
			if (IsInitialized) return;
			lock (lock_Init)
			{
				if (IsInitialized) return;

				ChangeStatus ("Initializing...");
				m_Disposables.Add (m_SendMessageService = AppServiceLocator.Instance.GetImpl<ISendMessageService> ());
				m_Disposables.Add (myLaunchMarketplaceAppDetailsService = AppServiceLocator.Instance.GetImpl<ILaunchMarketplaceAppDetailsService> ());

				m_Disposables.Add (m_GeoCoordinateProvider = AppServiceLocator.Instance.GetImpl<IGeoCoordinateProvider> ());
				m_GeoCoordinateProvider.GeoCoordinateUpdated += OnGeoCoordinateUpdated;
				m_GeoCoordinateProvider.CouldNotStart += OnGeoCoordinateProviderCouldNotStart;
				ChangeStatus ("Starting Location Services");
				m_GeoCoordinateProvider.StartAsync ();

				m_Dao = AppServiceLocator.Instance.GetImpl<ILocationSenderDao> ();
				Settings = m_Dao.LoadSettings ();
				if (Settings == null)
				{
					Settings = CreateNewSettings ();
					SaveSettings ();
				}

				IsInitialized = true;
				Initialized ();

				if (m_AppHelper.LaunchStatus == AppLaunchStatus.IsFresh)
					Settings.NumRuns++;

				ChangeStatus ("Initialized, waiting for Location...");
			}
		}

		public void Dispose ()
		{
			if (IsDisposed) return;

			lock (lock_Dispose)
			{
				if (IsDisposed) return;
				ChangeStatus ("Diposing...");

				if (m_GeoCoordinateProvider != null)
					m_GeoCoordinateProvider.Stop ();

				foreach (var disp in m_Disposables)
					disp.Dispose ();
				
				m_Disposables.Clear ();

				IsDisposed = true;
				ChangeStatus ("Disposed.");
			}
		}
		#endregion

		#region Event handlers
		private static void OnUnhandledException (Object sender, HandledEventArgs<Exception> e)
		{
			s_ExceptionFileCheckService.LogException (e.Item);
		}

		private void OnLaunching ()
		{
			Launching ();
		}

		private void OnSuspending ()
		{
			SaveSettings ();
			Suspending ();
		}

		private void OnResuming ()
		{
			Resuming ();
		}

		private void OnResumingFromTombstone ()
		{
			ResumingFromTombstone ();
		}

		private void OnClosing ()
		{
			SaveSettings ();
			Closing ();
		}

		private void OnGeoCoordinateUpdated (GeoCoordinate pos)
		{
			CurrentPosition = pos;
			GeoCoordinateUpdated (pos);
			ChangeStatus ("{0} @ {1:HH:mm:ss}".FormatWith (pos.ToString (GeoCoordinateFormat.LatLongAlt), pos.Timestamp));
		}

		private void OnGeoCoordinateProviderCouldNotStart ()
		{
			LocationServiceUnavailable = true;
			OnLocationServiceUnavailable ();
			ChangeStatus ("Unable to start Location Services");
		}
		#endregion

		#region Main Functionality
		public void SaveSettings ()
		{
			m_Dao.SaveSettings (Settings);
		}

		public IEnumerable<String> GetLastRecipients ()
		{
			return m_Dao.LoadRecipients ();
		}

		public GeoCoordinate GetLastGeoCoordinate ()
		{
			return m_Dao.LoadGeoCoordinate ();
		}

		public GeoCoordinateFormat GetLastGeoCoordinateFormat ()
		{
			return m_Dao.LoadGeoCoordinateFormat ();
		}

		public void SendMessage (IList<String> recipients, GeoCoordinateFormat format)
		{
			recipients.ThrowIfNull ("recipients");
			if (format == GeoCoordinateFormat.None) throw new ArgumentException ("format cannot be null");
			var pos = CurrentPosition;
			pos.ThrowIfNull ("pos");

			ChangeStatus ("Sending...");
			m_Dao.SaveRecipients (recipients);
			m_Dao.SaveGeoCoordinate (pos);
			m_Dao.SaveGeoCoordinateFormat (format);

			m_SendMessageService.Send (recipients,
			                           "Here I Am!\r\n{0}".FormatWith (
										pos.ToString (format)));
			ChangeStatus ("Sent.");
		}

		public void SendLastMessage ()
		{
			var recipients = m_Dao.LoadRecipients ().ToList ();
			var format = m_Dao.LoadGeoCoordinateFormat ();
			SendMessage (recipients, format);
		}
		#endregion

		#region Misc functionality
		public void LaunchMarketplaceAppDetails ()
		{
			myLaunchMarketplaceAppDetailsService.Launch ();
		}
		#endregion

		#region Private functionality
		private void ChangeStatus (String status)
		{
			Status = status;
			StatusChanged (Status);
		}

		private static LocationSenderSettings CreateNewSettings ()
		{
			var settings = AppServiceLocator.Instance.GetImpl<LocationSenderSettings> ();
			settings.NumRuns = 0;
			settings.UserApprovedLocationServices = false;
			settings.AskToRate = true;
			return settings;
		}
		#endregion
	}
}
